// => Matrix

#pragma once

#ifndef __MATRIX_H__
#define __MATRIX_H__

// Includes

#include <stddef.h>

// Types

/// @type: Matrix
/// > real number matrix
/// @content: <data> the data of matrix [ double * ]
/// @content: <size> the dimensions of matrix [ size_t[2] ]
typedef struct Matrix {
  double *data;
  size_t size[2];
} Matrix;

/// @type: Orientation
/// > orientaion of matrix or vector
/// @item: <ROW> by row
/// @item: <COL> by column
typedef enum Orientation { ROW = 0, COL = 1 } Orientation;

// Functions

/// @func: InitMatrixFromArray
/// >> init a matrix from a double array
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @param: <arr> the array [ double * ]
/// @param: <ort> the orientation of matrix [ Orientation ]
/// @return: the matrix from array [ Matrix * ]
/// @descript:
///   * exit when `arr` is a null pointer
///   * if `ort` is 0, then matrix will be filled by row
///   * if `ort` is 1, then matrix will be filled by column
Matrix *InitMatrixFromArray(size_t row, size_t col, double *arr,
                            Orientation ort);

/// @func: GetMatrixValue
/// >> get the value at a specific position in the matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @param: <row> the row position [ size_t ]
/// @param: <col> the column position [ size_t ]
/// @return: the value at the specific position [ double ]
/// @descript:
///   * `row` and `col` start from 1
///   * exit when `mat` is a null pointer
///   * exit when the position is out of boundary
double GetMatrixValue(const Matrix *mat, size_t row, size_t col);

/// @func: SetMatrixValue
/// >> set the value at a specific position in the matrix
/// @param: <mat> the matrix [ Matrix * ]
/// @param: <row> the row position [ size_t ]
/// @param: <col> the column position [ size_t ]
/// @param: <val> the value [ double ]
/// @descript:
///   * `row` and `col` start from 1
///   * exit when `mat` is a null pointer
///   * exit when the position is out of boundary
void SetMatrixValue(Matrix *mat, size_t row, size_t col, double val);

/// @func: Zeros
/// >> get a zeros matrix with specific size
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @return: a matrix fill with zeros [ Matrix * ]
Matrix *Zeros(size_t row, size_t col);

/// @func: InitMatrixDiag
/// >> init a diagonal matrix
/// @param: <arr> the value of diagnal element [ double * ]
/// @param: <len> the actual size of the matrix [ size_t ]
/// @return: the diagonal matrix [ Matrix * ]
/// @descript: exit when `arr` is a null pointer
Matrix *InitMatrixDiag(double *arr, size_t len);

/// @func: Eye
/// >> init an identity matrix
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @return: an identity matrix [ Matrix * ]
Matrix *Eye(size_t row, size_t col);

/// @func: InitRandIntMatrix
/// >> init a matrix with all random integers
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @param: <lb> the lower boundary [ int ]
/// @param: <ub> the upper boundary [ int ]
/// @param: <seed> the random seed for srand [ unsigned int ]
/// @return: the matrix [ Matrix * ]
/// @descript:
///   * range is [`lb`, `ub`], include endpoints
///   * exit when `ub` is smaller than `lb`
Matrix *InitRandIntMatrix(size_t row, size_t col, int lb, int ub,
                          unsigned int seed);

/// @func: InitRandRealMatrix
/// >> init a matrix with all random integers
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @param: <lb> the lower boundary [ double ]
/// @param: <ub> the upper boundary [ double ]
/// @param: <seed> the random seed for srand [ unsigned int ]
/// @return: the matrix [ Matrix * ]
/// @descript:
///   * range is [`lb`, `ub`], include endpoints
///   * exit when `ub` is smaller than `lb`
Matrix *InitRandRealMatrix(size_t row, size_t col, double lb, double ub,
                           unsigned int seed);

/// @func: GetMatrixDiag
/// >> get all the diagonal element of the matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: a column vector [ Matrix * ]
/// @descript: exit when `mat` is a null pointer
Matrix *GetMatrixDiag(const Matrix *mat);

/// @func: GetMatrixRank
/// >> get matrix rank
/// @param: <mat> the matrix [const Matrix * ]
/// @return: the rank of the matrix [ size_t ]
/// @descript: exit when `mat` is a null pointer
size_t GetMatrixRank(const Matrix *mat);

/// @func: TransposeMatrix
/// >> transpose a matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @param: the transposed matrix [ Matrix * ]
Matrix *TransposeMatrix(const Matrix *mat);

/// @func: PrintMatrix
/// >> print matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @descript: exit when `mat` is a null pointer
void PrintMatrix(const Matrix *mat);

/// @func: DestroyMatrix
/// >> deconstruct a matrix
/// @param: <mat> the matrix [ Matrix * ]
void DestroyMatrix(Matrix *mat);

/// @func: MatrixAdd
/// >> add up two matrices
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: the sum of two matrices [ Matrix * ]
/// @descript:
///   * exit when `m1` or `m2` is a null pointer
///   * exit when the size of `m1` and `m2` are not matched
Matrix *MatrixAdd(const Matrix *m1, const Matrix *m2);

/// @func: ScalarMatrixAdd
/// >> add up a matrix and a real number
/// @param: <val> the real number [ double ]
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the sum [ Matrix * ]
/// @descript: exit when `mat` is a null pointer
Matrix *ScalarMatrixAdd(double val, const Matrix *mat);

/// @func: ScalarMatrixMul
/// >> multiply a matrix and a real number
/// @param: <val> the real number [ double ]
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the product [ Matrix * ]
/// @descript: exit when `mat` is a null pointer
Matrix *ScalarMatrixMul(double val, const Matrix *mat);

/// @func: ScalarMatrixDiv
/// >> divide a matrix and a real number
/// @param: <mat> the matrix [ const Matrix * ]
/// @param: <val> the real number [ double ]
/// @return: the product [ Matrix * ]
/// @descript:
///   * exit when `mat` is a null pointer
///   * exit when `val` is zero
Matrix *ScalarMatrixDiv(const Matrix *mat, double val);

/// @func: MatrixMul
/// >> multiply two matrices
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: the product of two matrices [ Matrix * ]
/// @descript:
///   * exit when `m1` or `m2` is a null pointer
///   * exit when the size of `m1` and `m2` are not matched
Matrix *MatrixMul(const Matrix *m1, const Matrix *m2);

/// @func: InnerProduct
/// >> inner product for matrix
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: the inner product of two matrices [ Matrix * ]
/// @descript:
///   * exit when `m1` or `m2` is a null pointer
///   * exit when the size of `m1` and `m2` are not matched
Matrix *InnerProduct(const Matrix *m1, const Matrix *m2);

/// @func: VectorNorm
/// >> get vector norm
/// @param: <vec> the vector [ const Matrix * ]
/// @param: <t> the type of norm [ const char * ]
/// @return: the norm of vector [ double ]
/// @descript:
///   * exit when `vec` is a null pointer
///   * `t` can only be `euc`, `one` or `inf`
double VectorNorm(const Matrix *vec, const char *t);

/// @func: MatrixTrace
/// >> get the trace of matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the trace  [ double ]
/// @descript:
///   * exit when `mat` is a null pointer
///   * exit when `mat` is not a square matrix
double MatrixTrace(Matrix *mat);

/// @func: IsEqualMatrix
/// >> test two matrix whether equal
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: 1 for true, 0 for false [ _Bool ]
_Bool IsEqualMatrix(const Matrix *m1, const Matrix *m2);

/// @func: IsSymmetricMatrix
/// >> test a matrix whether a symmetric matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: 1 for true, 0 for false [ _Bool ]
_Bool IsSymmetricMatrix(const Matrix *mat);

/// @func: IsAntisymmetricMatrix
/// >> test a matrix whether a antisymmetric matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: 1 for true, 0 for false [ _Bool ]
_Bool IsAntisymmetricMatrix(const Matrix *mat);

/// @func: IsNormalMatrix
/// >> test a matrix whether a normal matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: 1 for true, 0 for false [ _Bool ]
_Bool IsNormalMatrix(const Matrix *mat);

/// @func: IsOrthogonalMatrix
/// >> test a matrix whether a orthogonal matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: 1 for true, 0 for false [ _Bool ]
_Bool IsOrthogonalMatrix(const Matrix *mat);

/// @func: MatrixDeterminant
/// >> get the determinant of matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the determinant [ double ]
/// @descript:
///   * exit when `mat` is a null pointer
///   * exit when `mat` is not a square matrix
double MatrixDeterminant(const Matrix *mat);

/// @func: InverseMatrix
/// >> get the inverse of matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the inverse matrix [ Matrix * ]
/// @descript:
///   * exit when `mat` is not a square matrix;
///   * exit when `mat` is a null pointer
///   * exit when `mat` is not inversible
Matrix *InverseMatrix(const Matrix *mat);

#endif
